package org.apache.tinkerpop.gremlin.server.handler;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpMessage;
import org.apache.tinkerpop.gremlin.server.GraphChannelizer;
import org.apache.tinkerpop.gremlin.server.channel.GraphWebSocketChannelizer;
import org.apache.tinkerpop.gremlin.server.util.GraphServerGremlinExecutor;

import static org.apache.tinkerpop.gremlin.server.AbstractChannelizer.PIPELINE_HTTP_AGGREGATOR;
import static org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer.PIPELINE_AUTHENTICATOR;
import static org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer.PIPELINE_REQUEST_HANDLER;

@ChannelHandler.Sharable
public class GraphWsAndHttpChannelizerHandler extends ChannelInboundHandlerAdapter {

  private final GraphWebSocketChannelizer graphWebSocketChannelizer =
      new GraphWebSocketChannelizer();
  private HttpGremlinEndpointHandler httpGremlinEndpointHandler;

  public void init(
      final GraphServerGremlinExecutor graphServerGremlinExecutor,
      final HttpGremlinEndpointHandler httpGremlinEndpointHandler) {
    // WebSocketChannelizer has everything needed for the http endpoint to work
    graphWebSocketChannelizer.init(graphServerGremlinExecutor);
    this.httpGremlinEndpointHandler = httpGremlinEndpointHandler;
  }

  public GraphChannelizer getGraphWebSocketChannelizer() {
    return graphWebSocketChannelizer;
  }

  public void configure(final ChannelPipeline pipeline) {
    graphWebSocketChannelizer.configure(pipeline);
  }

  @Override
  public void channelRead(final ChannelHandlerContext ctx, final Object obj) {
    final ChannelPipeline pipeline = ctx.pipeline();
    if (obj instanceof HttpMessage && !WebSocketHandlerUtil.isWebSocket((HttpMessage) obj)) {
      // if the message is for HTTP and not websockets then this handler injects the endpoint
      // handler in front
      // of the HTTP Aggregator to intercept the HttpMessage. Therefore the pipeline looks like this
      // at start:
      //
      // IdleStateHandler -> HttpResponseEncoder -> HttpRequestDecoder ->
      //    WsAndHttpChannelizerHandler -> HttpObjectAggregator ->
      //    WebSocketServerCompressionHandler -> WebSocketServerProtocolHandshakeHandler -> (more
      // websockets)
      //
      // and shifts to (setting aside the authentication condition):
      //
      // IdleStateHandler -> HttpResponseEncoder -> HttpRequestDecoder ->
      //    WsAndHttpChannelizerHandler -> HttpObjectAggregator ->
      //    HttpGremlinEndpointHandler ->
      //    WebSocketServerCompressionHandler - WebSocketServerProtocolHandshakeHandler -> (more
      // websockets)
      if (null != pipeline.get(PIPELINE_AUTHENTICATOR)) {
        pipeline.remove(PIPELINE_REQUEST_HANDLER);
        final ChannelHandler authenticator = pipeline.get(PIPELINE_AUTHENTICATOR);
        pipeline.remove(PIPELINE_AUTHENTICATOR);
        pipeline.addAfter(PIPELINE_HTTP_AGGREGATOR, PIPELINE_AUTHENTICATOR, authenticator);
        pipeline.addAfter(
            PIPELINE_AUTHENTICATOR, PIPELINE_REQUEST_HANDLER, this.httpGremlinEndpointHandler);
      } else {
        pipeline.remove(PIPELINE_REQUEST_HANDLER);
        pipeline.addAfter(
            PIPELINE_HTTP_AGGREGATOR, PIPELINE_REQUEST_HANDLER, this.httpGremlinEndpointHandler);
      }
    }
    ctx.fireChannelRead(obj);
  }

  @Override
  public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
    ctx.close();
  }
}
